import { jsPDF } from "jspdf";
// 解决中文乱码
import "./simfang-normal"

export default class createPDF {
    constructor(config={}){
        let {orientation='p',format="a4",unit="mm",compress=true} = config
        this.doc = new jsPDF({orientation,format,unit,compress}) // compress是生成压缩的PDF
        // 解决中文乱码
        this.doc.setFont('simfang')
        this.pageNum = 1
        this.pageWidth = this.doc.getPageWidth(1) // this.doc.getPageWidth() 无参数默认获取当前页（源码）
        this.pageHeight = this.doc.getPageHeight(1)
        let {fontName,fontStyle} = this.doc.getFont()
        this.fontName = fontName
        this.fontStyle = fontStyle
        this.orientation = orientation
        this.format = format
    }
    //  全局变量
    doc = null // jsPDF对象实例
    format='a4' // 纸张规格
    orientation='p' // 纸张方向
    pageWidth = 0 // 页宽
    pageHeight = 0 // 页高
    contentTop = 20 // 上边距
    get contentBottom(){ // 下边距（实际位置）
        return this.pageHeight - this.contentTop
    }
    get contentWidth () {
        return this.pageWidth-this.contentLeft*2
    }
    get contentHeight () {
        return this.pageHeight-this.contentTop*2
    }
    contentLeft = 20 // 左边距
    pageNum = 1 // 页码从1开始
    currentPage = 1 // 当前页
    currentPositionY = this.contentTop // 当前实际纵向位置

    fontName = "" // 字体标识 ："times".
    fontStyle = "" // 字体风格 ："normal" "italic"
    fontWeight = "400" // 字体粗细 "normal" "bold" | 400 700
    CharSpace = 0 // 字间距默认是0

    headerData = null // 缓存表头数据，分页时用
    
    // 生成表格
    createTable = (data) => {
        this.headerData = data.headerData
        // data.y是表头到contentTop的距离,但返回出来的是实际的竖向位置
        let tbY = this.createTH(this.headerData,(data.y || 0)+this.contentTop)
        // 获取表头字段顺序
        let dataOrder = this.getLeafKeyList(this.headerData.data)
        let tableY = this.createTB(data.bodyData,dataOrder,tbY)
        //返回表格结尾位置
        return this.currentPositionY = tableY
    }
    // 生成表头
    createTH = ({data=[],config={}},thY) => {
        return this.createTHRow(data,this.useTableConfig(config),thY)
    }
    // 生成表体
    // offsetIndex表示存在特殊行时，调整不同页面的特殊行的正确位置，就是缓存取余后的值，
    // 由于下标从0开始，第0行不是特殊行，所以刚开始就要偏移一位，即offsetIndex=1
    createTB = ({data=[],config={}},dataOrder,tbY,offsetIndex=1) => {
        let rowConfig = config.rowConfig
        let tbConfig = {}
        // 如果不存在行配置，则设置整体样式
        if(!rowConfig){
            tbConfig = this.useTableConfig(config)
        }
        for(let i=0,len=data.length;i<len;i++){
            // 数组中的对象转数组
            let arr = []
            dataOrder.forEach(val => {
                arr.push(data[i][val])
            })
            // 如果存在行配置，则每行都要执行一次，nf表示偏移量，如果不存在nf为0即不偏移
            let nf = 0
            if(rowConfig){
                // 拷贝一份config
                let obj = {}
                // 下标从0开始，取余需要+1
                nf = (i+offsetIndex) % rowConfig.index
                if(nf === 0){
                    for(let key in config){
                        if(key === 'rowConfig'){
                            continue
                        }
                        obj[key] = key in rowConfig ? rowConfig[key] : config[key]
                    }
                }else{
                    // 除了指定的行，其他行还用主要配置
                    obj = { ...config }
                    delete obj.rowConfig
                }
                tbConfig = this.useTableConfig(obj)
            }
            tbY = this.createTBRow(arr,tbConfig,tbY)
            if(tbY === 'end'){
                this.addPage()
                tbY = this.createTH(this.headerData,this.contentTop)
                return this.createTB({data:data.slice(i),config},dataOrder,tbY,nf)
            }
        }
        return tbY
    }
    // 使用表格配置
    useTableConfig = (config={}) => {
        let padding = config.padding || 0 // 内边距
        let fontSize = config.fontSize || 16 //字体大小
        let LineWidth = config.LineWidth || 0   // 边框宽度
        let vertical = config.vertical || "center" // 垂直
        let align = config.align || "left" // 水平 left,center,   right,justify,默认left
        let cellBGC = config.cellBGC || "#c0c0c0" //背景颜色
        let frameColor = config.frameColor || "#000000" //边框颜色
        let textColor = config.textColor || "red" //文字颜色
        let fontName = config.fontName || this.fontName
        let fontStyle = config.fontStyle || this.fontStyle
        let fontWeight = config.fontWeight || this.fontWeight
        let rowHeight = config.rowHeight || 10 //行高
        let CharSpace = config.CharSpace || 0 // 字间距
        let lineHeight = config.lineHeight || 1.15 // 行间距

        if(config.fontWeight || config.fontStyle ||config.fontName){
            this.doc.setFont(fontName,fontStyle,fontWeight)
        }
        this.doc.setFontSize(fontSize)  // 内容字体大小
        this.doc.setTextColor(textColor)  // 内容字体颜色
        this.doc.setLineWidth(LineWidth)
        this.doc.setDrawColor(frameColor)  // 边框颜色
        this.doc.setCharSpace(CharSpace)  // 字间距
        this.doc.setLineHeightFactor(lineHeight)  // 行间距
        return {
            padding,
            vertical,
            align,
            cellBGC,
            rowHeight
        }
    }
    // 创建头行
    createTHRow = (data,config={},Y) => {
        let {padding,vertical='center',align='center',cellBGC,rowHeight} = config
        // 表头字段数
        let subLen = 0
        // 表头格子数
        subLen = this.getLeafNumber(data)
        // cell基本设置
        let width = (this.pageWidth-this.contentLeft*2)/subLen // cell的宽
        let maxWidth = width-(padding*2)    // 内容最大宽度
        let {h} = this.doc.getTextDimensions('1') // 单个字体高度

        let len = data.length
        // 把有children的整体字段找到，并记录坐标，返回该字段的整体宽高的数组
        let SpecialFieldArr = []
        let SpecialIndexArr = []
        for(let i=0;i<len;i++){
            if(data[i].children && data[i].children.length){
                let obj = this.getSpecialField(data[i],{cellWidth:width,padding})
                // 存到对应的位置
                SpecialFieldArr[i] = obj
                // 统一管理，方便判断
                SpecialIndexArr.push(i)
            }
        }
        
        let RCHWArr = [] // 每行内容高度和宽度数组，先放高度，len后放宽度
        data.forEach((item,index) => {
            if(SpecialIndexArr.includes(index)){
                return
            }
            let {h:th,w:tw} = this.doc.getTextDimensions(item.value || '',{maxWidth})
            RCHWArr[index] = th
            RCHWArr[index+len] = tw
            let height = th+padding*2
            if(height>rowHeight){
                rowHeight = height
            }
        })
        // 分开比较，因为特殊字段绘制的逻辑不同，所需参数也不同
        SpecialFieldArr.forEach(item => {
            if(item.height>rowHeight){
                rowHeight = item.height
            }
        })
        // 可能存在多个特殊字段，或者常规字段的高大于特殊字段，所以要根据新的rowHeight重新赋值特殊字段高度
        let setHeight = (field,rowHeight) => {
            field.height = rowHeight
            if(field.children && field.children.length){
                field.children.forEach(item => {
                    setHeight(item,rowHeight-field.cellHeight)
                })
            }
        }
        SpecialFieldArr.forEach(item => {
            if(item.height < rowHeight){
                setHeight(item,rowHeight)
            }
        })
        if(Y+rowHeight > this.contentBottom){
            return 'end'
        }
        let cellTop = Y
        let textX,textY
        textX = textY = 0
        let distanceLeft = 0
        for(let i=0;i<len;i++){
            let cellLeft = this.contentLeft+distanceLeft
            let cellTextAlign = "left" 
            // 如果是特殊字段的实现方式
            if(SpecialIndexArr.includes(i)){
                this.drawSpecialField(SpecialFieldArr[i],{
                    vertical,
                    align,
                    cellBGC,
                    cellLeft,
                    cellTop,
                    cellTextAlign,
                    h
                })
                distanceLeft += SpecialFieldArr[i].width
                continue
            }
            // 一般情况
            if(vertical === "center"){
                textY = cellTop+h-1+(rowHeight-RCHWArr[i])/2
            }
            if(align === "left"){
                textX = cellLeft+padding
            }
            if(align === "center"){
                // i+len 是宽度的位置
                textX = cellLeft+width/2-( RCHWArr[i+len]/2 )
                if(RCHWArr[i] > h){
                    textX += RCHWArr[i+len]/2
                    // 文字居中时，初始点0变成为文字宽度的中间位置，所以不需要padding
                    cellTextAlign = "center"
                }
            }
            this.drawCell(cellLeft,cellTop,width,rowHeight,cellBGC)
            this.doc.text(data[i].value || '',textX,textY,{maxWidth,align:cellTextAlign})

            // 判断左侧距离
            distanceLeft += width
        }
        // 返回结束位置Y
        return Y+rowHeight
    }
    // 获取叶子节点数
    getLeafNumber = (arr) => {
        let num = 0
        let fn = (arr)=>{
            for(let i=0,len=arr.length;i<len;i++){
                if(arr[i].children && arr[i].children.length){
                    fn(arr[i].children)
                }else{
                    // 没有children一定是叶子节点
                    num++
                }
            }
        }
        fn(arr)
        return num
    }
    // 获取节点有多少层
    getFieldFloor = (field) => {
        // 默认一层
        let num = 1
        let fn = (val,floor) => {
            num = floor
            if(val.children && val.children.length){
                for(let i=0;i<val.children.length;i++){
                    fn(val.children[i],floor+1)
                }
            }
        }
        fn(field,num)
        return num
    }
    // 获取表头特殊字段宽高属性,高度规则是先根据有多少层切分padding，跟据新padding和selfHeight算出,最大的高
    // 再反向计算没到最大高的叶子节点的高度，就是除去必须的高度叠加，剩下的高度全部加到叶子节点
    getSpecialField = (field,{cellWidth,padding},height=0) => {
        // 拿到整体宽（即自身宽）
        let width = this.getLeafNumber([field]) * cellWidth
        // 算出有多少层
        let floor = this.getFieldFloor(field)
        // 拿到整体高
        if(!height){
            // 根据层数得到新padding,只获得一次即可
            padding = padding/floor
            height = this.getFieldHeight(field,{padding,cellWidth})
        }
        // 拿自身高selfHeight
        let {h:th,w:tw} = this.doc.getTextDimensions(field.value || '',{maxWidth: width-padding*2})
        let textWidth = tw
        let cellHeight = th+padding*2
        let obj = {
            key:field.key,
            value:field.value,
            textWidth,
            textHeight:th,// 最后一行特殊，需要取这个值
            cellHeight,
            width, // 整体的宽,也是cellWidth
            height, // 整体的高
            padding
        }
        // 递归子字段信息
        let len = 0
        if(field.children && (len = field.children.length)){
            let children = field.children
            let arr = []
            for(let i=0;i<len;i++){
                arr.push(
                    this.getSpecialField(
                        children[i],
                        {
                            cellWidth,
                            padding
                        },
                        height-cellHeight
                    )
                )
            }
            obj.children = arr
        }
        return obj
    }
    // 获取字段高度（DFS，每个分支高度累加的方式求出最大值）
    getFieldHeight = (field,{padding,cellWidth}) => {
        let fn = (val,height=0) => {
            let cellNum = 1
            cellNum = this.getLeafNumber([val])
            let {h:th} = this.doc.getTextDimensions(val.value || '',{maxWidth: cellWidth*cellNum-padding*2})
            // 求出当前格需要的高度并累加
            height += th+padding*2
            // 如果有子继续向队列中添加
            let hArr = [] 
            if(val.children && val.children.length){
                for(let j=0,slen=val.children.length;j<slen;j++){
                    hArr.push(fn(val.children[j],height))
                }
            }
            return hArr.length ? Math.max(...hArr) : height
        }
        return fn(field)
    }
    // 绘制特殊字段
    drawSpecialField = (field,{vertical,align,cellBGC,cellLeft,cellTop,cellTextAlign="left",h}) => {
        let {value,textWidth,textHeight,cellHeight,width,height,padding,children} = field
        let textX, textY
        textX = textY = 0
        if(vertical === 'center'){
            textY = cellTop+h-1+((!!children ? cellHeight : height) - textHeight)/2
        }
        if(align === "left"){
            textX = cellLeft+padding
        }
        if(align === "center"){
            // i+len 是宽度的位置
            textX = cellLeft+width/2-( textWidth/2 )
            if(textHeight > h){
                textX += textWidth/2
                // 文字居中时，初始点0变成为文字宽度的中间位置，所以不需要padding
                cellTextAlign = "center"
            }
        }
        this.drawCell(cellLeft,cellTop,width,(!!children ? cellHeight : height),cellBGC)
        this.doc.text(value || '',textX,textY,{maxWidth:textWidth,align:cellTextAlign})

        if(children && children.length){
            let subFieldWidth = 0
            for(let i=0;i<children.length;i++){
                this.drawSpecialField(
                    children[i],
                    {
                        vertical,align,cellBGC,
                        cellLeft:cellLeft+subFieldWidth,
                        cellTop:cellTop+cellHeight,
                        cellTextAlign,h
                    }
                )
                subFieldWidth += children[i].width
            }
        }
    }
    // 绘制表中的单元格
    drawCell = (cellLeft,cellTop,width,height,cellBGC) => {
        let style = 'FD'//FD,F填充内容，D填充边框,默认时S，只有边框
        if(cellBGC !== 'transparent'){
            this.doc.setFillColor(cellBGC);// cell 底色，只生效一次，所以要写里面
        }else{
            style = 'S'
        }
        this.doc.rect(cellLeft,cellTop,width,height,style)
    }
    //获取表头叶子节点key的数组
    getLeafKeyList = (data) =>{
        let res = []
        let fn = (arr) => {
            for(let i=0;i<arr.length;i++){
                if(arr[i].children && arr[i].children.length){
                    fn(arr[i].children)
                }else{
                    res.push(arr[i].key)
                }
            }
        }
        fn(data)
        return res
    }
    // 创建体行
    createTBRow = (data,config={},Y) => {
        let {padding,vertical='center',align='center',cellBGC,rowHeight} = config
        let len = data.length
        // cell基本设置
        let width = (this.pageWidth-this.contentLeft*2)/len // cell的宽
        let maxWidth = width-(padding*2)    // 内容最大宽度
        let {h} = this.doc.getTextDimensions('1') // 单个字体高度
        let RCHWArr = [] // 每行内容高度和宽度数组，先放高度，len后放宽度
        data.forEach((item,index) => {
            let {h:th,w:tw} = this.doc.getTextDimensions(item||'',{maxWidth})
            RCHWArr[index] = th
            RCHWArr[index+len] = tw
            let height = th+padding*2
            if(height>rowHeight){
                rowHeight = height
            }
        })
        if(Y+rowHeight > this.contentBottom){
            return 'end'
        }
        let cellTop = Y
        let textX,textY
        textX = textY = 0
        for(let i=0;i<len;i++){
            let cellLeft = this.contentLeft+width*i
            let cellTextAlign = "left" 
            if(vertical === "center"){
                textY = cellTop+h-1+(rowHeight-RCHWArr[i])/2
            }
            if(align === "left"){
                textX = cellLeft+padding
            }
            if(align === "center"){
                // i+len 是宽度的位置
                textX = cellLeft+width/2-( RCHWArr[i+len]/2 )
                if(RCHWArr[i] > h){
                    textX += RCHWArr[i+len]/2
                    // 文字居中时，初始点0变成为文字宽度的中间位置，所以不需要padding
                    cellTextAlign = "center"
                }
            }
            this.drawCell(cellLeft,cellTop,width,rowHeight,cellBGC)
            this.doc.text(data[i]||'',textX,textY,{maxWidth,align:cellTextAlign})
        }
        // 返回结束位置Y
        return Y+rowHeight
    }

    // 生成一行文字
    createFontLine = (text,config = {}) => {
        let {
            marginLeft = 0,
            marginTop = 0,
            fontName = this.fontName,
            fontStyle = this.fontStyle,
            fontWeight = this.fontWeight,
            maxWidth = this.pageWidth - this.contentLeft * 2,
            textColor = '#000000',
            align = 'left',
            fontSize = 12,
            lineHeight = 1.15,
            CharSpace = 0
        } = config
        if(fontWeight != this.fontWeight|| fontStyle != this.fontStyle||fontName != this.fontName){
            this.doc.setFont(fontName,fontStyle,fontWeight)
        }
        this.doc.setTextColor(textColor)
        this.doc.setFontSize(fontSize)
        this.doc.setCharSpace(CharSpace)
        this.doc.setLineHeightFactor(lineHeight)
        // 改完字体后再拿高度
        let {h} = this.doc.getTextDimensions('1') // 高都一样
        let {h:th,w:tw} =this.doc.getTextDimensions(text,{maxWidth})
        let textX = this.contentLeft+marginLeft
        let textY = this.currentPositionY+marginTop+h+1
        if (align === 'center') {
            if(th>h){
                textX = textX + tw/2
            }else{
                textX = this.pageWidth/2
            }
        }
        let positionEnd = textY+th-h-1
        if(positionEnd>this.contentBottom){
            this.addPage()
            return this.createFontLine(text,config)
        }
        this.doc.text(text || '',textX,textY,{maxWidth,align})
        return this.currentPositionY = textY+th-h-1
    }

    // 生成多行文字
    createFontLines = (textList,config = {}) => {
        let { rowSpacing=1.15,marginTop=0,linesMarginTop=20 } = config
        textList.forEach((item,index) => {
            config.marginTop = marginTop + index > 0 ? rowSpacing : linesMarginTop
            this.createFontLine(item,config)
        })
        return this.currentPositionY
    }

    // 添加图片
    createImage = (config={}) => {
        let url = ''
        if(typeof config === 'string'){
            url = config
            config = ''
        }
        let {imgUrl='', format='JPEG', x=0, y=0, width=this.pageWidth, height=this.pageHeight, alias, compression, rotation,positionFlag=false} = config || {}
        if(!url){
            url = imgUrl
        }
        if(!url){
            throw new Error('请输入图片url');
        }
        let img = new Image, data, ret={data: null, pending: true, position:y};
        img.src = url;
        return new Promise((resolve,reject)=>{
            img.onError = function() {
                reject(new Error('Cannot load image: "'+url+'"'))
            }
            img.onload = ()=>{
                let canvas = document.createElement('canvas');
                let n = 4
                document.body.appendChild(canvas);
                canvas.width = img.width * n;
                canvas.height = img.height * n;
        
                let ctx = canvas.getContext('2d');
                ctx.drawImage(img, 0, 0, img.width * n, img.height * n);
                let lowFormat = format.toLowerCase()
                data = canvas.toDataURL('image/'+lowFormat).slice(('data:image/'+lowFormat+';base64,').length);
                data = atob(data)
                // data = canvas
                document.body.removeChild(canvas);
        
                ret['data'] = data;
                ret['pending'] = false;
                this.doc.addImage(data, format, x, y, width, height, alias, compression, rotation);
                if(positionFlag){
                    this.currentPositionY = ret['position'] = y+height
                }
                resolve(ret)
            }
        })
    }

    // 页眉、页脚，在页面都生成完后调用
    createHF = () => {
        for(let i=1;i<=this.pageNum;i++){
            this.setPage(i)
            this.doc.setLineWidth(0);
            this.doc.setDrawColor(255,0,0);
            this.doc.line(0,this.contentTop,this.pageWidth,this.contentTop)
            this.doc.line(0,this.contentBottom,this.pageWidth,this.contentBottom)
        }
    }

    // 页面相关方法
    // 翻页
    addPage = (config={}) => {
        let {format=this.format,orientation=this.orientation} = config
        this.format = format
        this.orientation = orientation
        this.pageNum++
        this.currentPage++
        let temp = this.doc.addPage(this.format,this.orientation) // 需要先改变页面，再调用页面方法
        this.pageHeight = this.doc.getPageHeight()
        this.pageWidth = this.doc.getPageWidth()
        this.currentPositionY = this.contentTop
        return temp
    }
    // 选页
    setPage = (page) => {
        this.currentPage = page || 1
        let temp = this.doc.setPage(page)
        this.pageHeight = this.doc.getPageHeight()
        this.pageWidth = this.doc.getPageWidth()
        return temp
    }
}